#define _FILE_OFFSET_BITS  64

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "memalloc_inner.h"
size_t NEXT_ID;
void* THE_POINTER;
int INITIALIZED = 0;

void poisoning(void* ptr, size_t size)
{
	int poison = 0x12429394;
	int* i, *end;
	end = (int*) ptr + (size + sizeof(int) - 1) / sizeof(int) -1;
	// K: memset may be faster in general
	for (i = (int*)ptr; i != end; i++) {
		*i = poison;
	}
	switch (size % 4) {
	case 3:
		((char*) end)[2] = 0x93;
	case 2:
		((char*) end)[1] = 0x42;
	case 1:
		((char*) end)[0] = 0x12;
	}
	return;
}

int swap_defrag()
{
	list* i = delim->snext;
	off_t new_offset = 0;
	if (i->offset != 0) {
		if (!swap2swap(i->offset, new_offset, i->size)) {
			return 0;
		}
		i->offset = new_offset;
	}
	i = i->snext;
	while (i != delim) {
		new_offset = i->sprev->offset + i->sprev->size;
		if (!swap2swap(i->offset, new_offset, i->size)) {
			return 0;
		}
		i->offset = new_offset;
		i = i->snext;
	}
	return 1;
}

/*finds place in swap and puts to swap ring
returns either 0 if its impossible or 1 in other case*/

int setOff(list* entry)
{
	list *i;
	if (entry == NULL) {
		return 0;
	}
	if (entry->snext) {
		return 1;
	}

	if (delim->snext == delim) {
		entry->snext = delim;
		entry->sprev = delim;
		delim->snext = entry;
		delim->sprev = entry;
		entry->offset = 0;
		return 1;
	}

	/*TODO need to choose more wisely*/
	if (delim->snext->offset >= entry->size) {
		entry->snext = delim->snext;
		entry->sprev = delim;
		delim->snext->sprev = entry;
		delim->snext = entry;
		entry->offset = 0;
		return 1;
	}
	if (TOTAL - delim->sprev->offset >= delim->sprev->size + entry->size) {
		entry->snext = delim;
		entry->sprev = delim->sprev;
		entry->offset = delim->sprev->offset + delim->sprev->size;
		delim->sprev->snext = entry;
		delim->sprev = entry;
		return 1;
	}
	for (i = delim->sprev; i->sprev != delim; i = i->sprev) {
		if (i->offset - i->sprev->offset
			>= i->size + entry->size) {


			entry->snext = i;
			entry->sprev = i->sprev;
			i->sprev->snext = entry;
			i->sprev = entry;
			entry->offset = i->sprev->offset + i->sprev->size;
			return 1;
		}

	}
	
	return 0;

}

/*/swap all that you can swap.*/

int do_swaping()
{
	list* i = delim->mnext;
	while (i != delim) {
		if (i->refcnt == 0) {
			if (!mem2swap(i->ptr, i->offset, i->size)) {
				goto fail;
			}
			i = i->mnext;
			removeFromMRing(i->mprev);
		} else {
			i = i->mnext;
		}
	}
	return 1;
fail:
	return 0;
}

/*finds place in mem and puts to mem ring
returns either 0 if its impossible or 1 in other case*/

int setPtr(list* entry)
{
	list *i;
	if (entry == NULL) {
		return 0;
	}
	if (delim->mnext == delim) {
		entry->mnext = delim;
		entry->mprev = delim;
		delim->mnext = entry;
		delim->mprev = entry;
		entry->ptr = THE_POINTER;
		return 1;
	}
	/*TODO need to choose more wisely*/
	// K: Consider adding data to the delim pointer so that "if" block becomes unnecessary (moves into the "for" block).
	if ((size_t) delim->mnext->ptr - (size_t) THE_POINTER >= entry->size) {
		entry->mnext = delim->mnext;
		entry->mprev = delim;
		delim->mnext->mprev = entry;
		delim->mnext = entry;
		entry->ptr = THE_POINTER;
		return 1;
	}
	if ((size_t) THE_POINTER + MEM - (size_t) delim->mprev->ptr
		>= delim->mprev->size + entry->size) {
		
		
		entry->mnext = delim;
		entry->mprev = delim->mprev;
		entry->ptr = (void*)((size_t)delim->mprev->ptr + delim->mprev->size);
		delim->mprev->mnext = entry;
		delim->mprev = entry;
		return 1;
	}
	for (i = delim->mprev; i->mprev != delim; i = i->mprev) {
		if ((size_t) i->ptr - (size_t) i->mprev->ptr
			>= i->size + entry->size) {


			entry->mnext = i;
			entry->mprev = i->mprev;
			i->mprev->mnext = entry;
			i->mprev = entry;
			entry->ptr = (void*)((size_t)i->mprev->ptr + i->mprev->size);
			return 1;
		}

	}
	
	return 0;

}

int ma_init(size_t mem, off_t swap, const char* swap_path)
{
	if (INITIALIZED == 0) {
		lock(INITM);
		if (INITIALIZED == 0) {
			INITIALIZED = 1;
		} else {
			errno = EALREADY;
			unlock(INITM);
			return 0;
		}
	} else {
		errno = EALREADY;
		unlock(INITM);
		return 0;
	}
	// K: Maybe mem and swap should be checked to be non-zero?
	if (mem > swap) {
		errno = EBADR;
		goto fail;
	}

	struct rlimit rlim;
	getrlimit(RLIMIT_FSIZE, &rlim);
	if (rlim.rlim_cur < swap) {
		errno = E2BIG;
		goto fail;
	}
	THE_POINTER = malloc(mem + 1);
	if ((THE_POINTER == NULL) || (mem + 1 == 0)) {
		errno = ENOMEM;
		goto fail2alloc;
	}
	THE_POINTER =(void*)((char*)THE_POINTER+1);
	if (swap_init(swap_path, swap) == 0) {
		goto fail;
	}
	NEXT_ID = 1;
	if(DataInit(mem, swap, swap)==NULL){
		swap_deinit();
		goto fail;
	}
	unlock(INITM);
	return 1;
fail:
	free((char*)THE_POINTER-1);
fail2alloc:
	INITIALIZED = 0;
	unlock(INITM);
	return 0;
}

void ma_deinit()
{
	lock(GLOBALM);
	if (INITIALIZED == 0) {
		errno = ECANCELED;
		unlock(GLOBALM);
		return;
	}
	INITIALIZED = 0;
	swap_deinit();
	free((void*)((size_t)THE_POINTER - 1));
	DataDeinit();
	unlock(GLOBALM);
	return;
}

size_t ma_alloc(size_t sz)
{
	size_t id;
	lock(ALLOCM);
	if (INITIALIZED == 0) {
		errno = ECANCELED;
		goto fail;
	}
	if (TOTAL_USED + sz > TOTAL) {
		errno = ENOMEM;
		goto fail;
	}
	if (sz > MEM) {
		errno = E2BIG;
		goto fail;
	}
	id = NEXT_ID++;
	if (id == 0) {
		errno = EMLINK;
		goto fail;
	}
	TOTAL_USED += sz;
	if(addData(id,sz)==NULL){
		goto fail;
	}
	unlock(ALLOCM);
	return id;
fail:
	unlock(ALLOCM);
	return 0;
}

int ma_free(size_t id)
{
	list* entry;
	if(id==0){
		errno=EINVAL;
		goto fail;
	}
	lock(FREEM);
	if (INITIALIZED == 0) {
		errno = ECANCELED;
		goto fail;
	}
	entry = findListById(id);
	if (entry == NULL) {
		goto fail;
	}

	if (entry->refcnt != 0) {
		/*bad guys don't release memory before erase it :(*/
		errno = EBUSY;
		goto fail;
	}
	removeFromTRing(entry);
	unlock(FREEM);
	return 1;
fail:
	unlock(FREEM);
	return 0;

}

void* ma_get(size_t id)
{
	list* entry;
	if(id==0){
		errno=EINVAL;
		goto fail;
	}
	lock(GETM);
	if (INITIALIZED == 0) {
		errno = ECANCELED;
		goto fail;
	}
	if (id >= NEXT_ID) {
		errno = EFAULT;
		goto fail;
	}
	entry = findListById(id);
	if (entry == (list*) 0) {
		errno = EFAULT;
		goto fail;
	}
	if (entry->refcnt > 0) {
		goto finalize;
	}
	if (entry->size + MEM_USED > MEM) {
		if (!do_swaping()) {
			goto fail;
		}
		if (entry->size + MEM_USED > MEM) {
			errno = ENOMEM;
			goto fail;
		}
	}
	/*Known security issue can be fixed if after release refcount reach 0 make swaping*/
	/*It's not bug anymore. It's feature!*/
#ifndef DEBUG
	if (entry->mnext != NULL) {
		goto finalize;
	}
#endif
	if (!setPtr(entry)) {
		if (!do_swaping()) {
			goto fail;
		}
		if (!setPtr(entry)) {
			errno = ENOBUFS;
			goto fail;
		}
	}
	if (entry->snext != NULL) {
		if (!swap2mem(entry->offset, entry->ptr, entry->size)) {
			goto fail;
		}
	} else {
		if (!setOff(entry)) {
			swap_defrag();
			setOff(entry);
		}
#ifdef DEBUG
		poisoning(entry->ptr, entry->size);
#endif
	}


finalize:
	MEM_USED += entry->size;
	entry->refcnt++;
	unlock(GETM);
	return entry->ptr;
fail:
	unlock(GETM);
	return NULL;
}

/*
ma_get() возвращает указатель на участок памяти с внутренним идентификатором id.
До тех пор пока в отношении того же идентификатора не будет вызвана функция ma_release() гарантируется валидность этого указателя(SAFEMODE).
В случае ошибки возвращается NULL, а код ошибки записывается в errno
Коды ошибок
EFAULT - память с таким идентификатором ещё не выделена, или уже освобождена
ENOMEM - недостаточно места в оперативной памяти
ENOBUFS - память слишком фрагментирована

EBADF - ошибка файла подкачки
to be continued...
 */


int ma_release(size_t id)
{
	list* entry;
	if(id==0){
		errno=EINVAL;
		goto fail;
	}
	lock(RELM);
	if (INITIALIZED == 0) {
		errno = ECANCELED;
		goto fail;
	}
	entry = findListById(id);
	if (entry == (list*) 0) {
		goto fail;
	}
	if (entry->refcnt > 0) {
		entry->refcnt--;
// K: Why not only poisoning, but also mem2swap and removeFromMRing are included in this "ifdef" section?
// I: 'cause in release mode swappiness = 0
#ifdef DEBUG
		if (entry->refcnt == 0) {
			// K: return value of mem2swap is not being checked for errors.
			mem2swap(entry->ptr, entry->offset, entry->size);
			poisoning(entry->ptr, entry->size);
			removeFromMRing(entry);
		}
#endif
		unlock(RELM);
		return 1;
	}
	errno = EFAULT;
fail:
	unlock(RELM);
	return 0;
}
/*
ma_release() указывает на то что участок памяти с идентификатором id больше не будет использован и в случае необходимости его можно переместить в файл подкачки.
Если по этому id ранее был выдан указатель функцией get(id), то изменение памяти по этому указателю может привести к потерям внесённых изменений, либо порче кучи.

Возвращаемое значение:
1 - успешно выполнено.
0 - в качестве аргумента использован не валидный указатель. В этом случае errno присваивается код ошибки:

EFAULT - память с таким идентификатором ещё не выделена, или уже освобождена(с помощью ma_free() или ma_release()
 */
